/* MP3 module Test */

#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include "swis.h"

#define DiskSample_Version        0x52EC0
#define DiskSample_FileOpen       0x52EC2
#define DiskSample_StreamClose    0x52EC3
#define DiskSample_StreamCreate   0x52EC4
#define DiskSample_StreamSource   0x52EC5
#define DiskSample_StreamReceiver 0x52EC6
#define DiskSample_StreamPlay     0x52EC8
#define DiskSample_StreamPosition 0x52ECB
#define DiskSample_StreamVolume   0x52ECC
#define DiskSample_StreamIsReady  0x52ECD
#define DiskSample_StreamStatus   0x52ECE
#define DiskSample_StreamInfo     0x52ED0
#define DiskSample_StreamDecoding 0x52ED3

#define BLOCKSIZE 4096

typedef struct
{
	// buffer header
	int size;     // read only, size of the input buffer in bytes
	int start;    // read only, offset from start of inbuf.data,
	              // gives the beginning of the filled part of the buffer
	              // this value change in the background while DiskSample
	              // decodes the data and empties parts of the buffer
	int free;     // writable, offset from start of inbuf.data,
	              // gives the end of the filled part of the buffer
	              // you should update if as you fill the buffer
	int finished; // writable, tells DiskSample if more data is to be expected
	              // 0: more data will follow, 1: no more data will follow
	int dummy[12];
	// buffer data
	char data[1]; // actually it is data[inbuf.size]
} inbuf;

// The buffer is circular and the following conditions apply:
// 0 <= start < size
// 0 <= free < size
// if (start = free) the buffer is empty
// if (start < free) the buffer is filled from [start, free[
// if (start > free) the buffer is filled from [start, size[ and [0, free[
// the buffer may never be completely filled

typedef struct
{
	int filesize;
	union
	{
		int         filetype;
		const char* codecname;
	}   type;
	int flags;
} src_desc;

static FILE*  in_file = NULL;
static inbuf* inb = NULL;

static _kernel_oserror err_block =
{0, ""};

static _kernel_oserror* ErrorFromString(const char* pformat, ...)
{
	va_list arg;

	va_start(arg, pformat);
	vsnprintf(err_block.errmess, sizeof(err_block.errmess), pformat, arg);
	va_end(arg);

	return &err_block;
}

static _kernel_oserror* RMEnsure(const char* name, int vers, const char* path)
{
	_kernel_oserror* e;
	int* addr;
	const char* help;
	int count;
	int found_vers = -1;

	// find module in RMA
	e = _swix(OS_Module, _INR(0,1)|_OUT(3), 18, name, &addr);

	if (!e)
	{
		// check version
		help = (char*) addr;
		help += addr[5];
		count = 0;
		while(count < 16)
		{
			count++;
			if (*help == 9)
			{
				count += 7;
				count &= ~7;
			}
			help++;
		}
		if (help[1] == '.')
			found_vers = (help[0] - '0') * 100
			           + (help[2] - '0') * 10
			           + (help[3] - '0');
	}

	if (found_vers >= vers)
		return NULL;

	// load module in RMA
	e = _swix(OS_Module, _INR(0,1), 1, path);
	if (e) return e;


	// find module in RMA
	e = _swix(OS_Module, _INR(0,1)|_OUT(3), 18, name, &addr);
	if (e) return e;

	// check version
	found_vers = -1;
	help = (char*) addr;
	help += addr[5];
	count = 0;
	while(count < 16)
	{
		count++;
		if (*help == 9)
		{
			count += 7;
			count &= ~7;
		}
		help++;
	}
	if (help[1] == '.')
		found_vers = (help[0] - '0') * 100
		           + (help[2] - '0') * 10
		           + (help[3] - '0');

	if (found_vers < vers)
		return ErrorFromString("Version %d.%02d of module %s is required.", vers / 100, vers % 100, name);

	return NULL;
}

static int Keyboard_KeyPressed(void)
{
	int pa, pb;

	_swix(OS_Byte, _INR(0,2)|_OUTR(1,2), 129, 0, 0, &pa, &pb);
	if (!pb)
	{
		if (pa)
			return pa;

		// Ctrl keys are configured to return a leading 0
		_swix(OS_Byte, _INR(0,2)|_OUTR(1,2), 129, 0, 0, &pa, &pb);
		if (!pb && pa)
			return 0x100 + pa;
	}

	return -1;
}

static void fill_buffer(int blksize)
{
	int start = inb->start; // Because value may change in the background
	int n;

	n = start - inb->free;
	if (n <= 0)
		n += inb->size;

	if (feof(in_file))
	{
		// If end of file reached, mark the input buffer as completed
		inb->finished = 1;
	}
	else if (n > blksize)
	{
		// Free part may actually wrap from end to beginning of the buffer
		if (blksize > (inb->size - inb->free))
			blksize = inb->size - inb->free;
		// If enough free space inf buffer, read a block of data
		// but beware of never filling the buffer completely
		// as it would be interpreted as an empty buffer
		n = fread(&inb->data[inb->free], 1, blksize, in_file);
		// Update position of free pointer but use an intermediate variable
		// to avoid temporary corruption of free pointer (AudioMPEG
		// is working in the background and may work on the buffer
		// in the middle of the computation of the new free position
		n += inb->free;
		assert((n >= 0) && (n <= inb->size));
		if (n >= inb->size)
			n = 0;
		inb->free = n;
	}
}

int main(int argc, char **argv)
{
	int t, tin, tout;
	src_desc desc = {0, 0x1AD, 0};
	int handle = -1;
	int isready = 0;
	int status;
	int volume = 256;
	int pos;
	int blksize = BLOCKSIZE;
	int bStreaming = 0;
	int bBadSyntax = 0;
	int bIsUrl = 0;
	int bExtMode = 0;
	int dstMode = 0;
	char* file = NULL;
	char* outfile = NULL;
	char* proxy = NULL;
	char* ext = NULL;
	_kernel_oserror* e = NULL;
	_kernel_oserror err;

	for (t = 1; t < argc; t++)
	{
		if (argv[t][0] != '-')
		{
			if (!file)
			{
				file = argv[t];
				bIsUrl = (strncmp(file, "http://", 7) == 0);
			}
			else if (bIsUrl && !proxy)
			{
				proxy = argv[t];
			}
			else
			{
				bBadSyntax = 1;
			}
		}
		else if (argv[t][1] == 'b')
			bStreaming = 1;
		else if (argv[t][1] == 'v')
			volume = atoi(&argv[t][2]);
		else if (argv[t][1] == 'e')
			bExtMode = 1;
		else if (argv[t][1] == 'o')
		{
			if (argv[t][2] == 'o')
				dstMode = 1;
			else if (argv[t][2] == 'd')
				dstMode = 2;

			if ((t + 1) < argc)
			{
				outfile = argv[t+1];
				t += 1;
			}
			else
				bBadSyntax = 1;
		}
		else
			bBadSyntax = 1;
	}

	if (file == NULL)
		bBadSyntax = 1;

	if (bBadSyntax)
	{
		fprintf(stderr, "DiskPlay is used to play files or internet radios with the DiskSample module\n");
		fprintf(stderr, "Internet radios are specified by giving an url in the form:\n");
		fprintf(stderr, "  http://[<user>[:<password>]@]<hostname>[:<port>]/[<path>]\n");
		fprintf(stderr, "  e.g. http://live.urn1350.net:8080/urn_high.ogg\n");
		fprintf(stderr, "which may be optionally followed by a proxy in the form:\n");
		fprintf(stderr, "http://[<user>[:<password>]@]<hostname>[:<port>]/\n");
		fprintf(stderr, "  e.g. http://proxy.skynet.be:8080/\n");
		fprintf(stderr, "Switches:\n");
		fprintf(stderr, " -v<volume> volume at witch to play [0...256...1024]\n");
		fprintf(stderr, "            values higher than 256 amplify the sound\n");
		fprintf(stderr, " -b         (internal use) to play the file through an internal buffer\n");
		fprintf(stderr, " -e         passes the file's extension as codec name to use\n");
		fprintf(stderr, " -oo <file> store undecoded data in file\n");
		fprintf(stderr, " -od <file> store decoded data in WAVE file\n");
		fprintf(stderr, "Syntax: DiskPlay [-v<volume>][-b][-e] <file> [-o[o|d] <file>]\n");
		fprintf(stderr, "        DiskPlay [-v<volume>] <url> [<proxy>] [-o[o|d] <file>]\n");
		return EXIT_FAILURE;
	}

	if (bStreaming && bExtMode)
	{
		fprintf(stderr, "Switches -b and -e cannot be used together!\n");
		return EXIT_FAILURE;
	}

	if (bIsUrl && (bStreaming || bExtMode))
	{
		fprintf(stderr, "Switches -b and -e cannot be used when an url is specified!\n");
		return EXIT_FAILURE;
	}

	if ((volume < 0) || (volume > 1024))
	{
		fprintf(stderr, "Invalid volume specified!\n");
		return EXIT_FAILURE;
	}

	// Version 0.40 and above is required
	e = RMEnsure("DiskSample", 40, "System:Modules.Audio.DiskSample");
	if (e)
	{
		fprintf(stderr, "%s\n", e->errmess);
		return EXIT_FAILURE;
	}

	// Extract file extension
	if (bExtMode)
	{
		ext = strchr(file, '/');
		if (ext < strchr(file, '.'))
			ext = NULL;

		if (!ext)
		{
			bExtMode = 0;
			printf("Filename as no extension, switch -e ignored");
		}
		else ext += 1;
	}

	// Disable Escape
	_swix(OS_Byte, _INR(0,2), 0xe5, 1, 0);

	// False loop for error catching
	while (!e)
	{
		// Create DiskSample stream and set its volume
		e = _swix(DiskSample_StreamCreate, _INR(0,1)|_OUT(0), 0, 64 /* KB */, &handle);
		if (e) break;

		e = _swix(DiskSample_StreamReceiver, _INR(0,3), handle, dstMode, outfile, 0);
		if (e) break;
		e =_swix(DiskSample_StreamVolume, _INR(0,1), handle, volume);

		// Assign the appropriate input source to the stream
		if (bStreaming)
		{
			in_file=fopen(file, "r");

			if (!in_file)
			{
				snprintf(err.errmess, sizeof(err.errmess), "Could not open file '%s'", file);
				e = &err;
				break;
			}

			fseek(in_file, 0, SEEK_END);
			desc.filesize = (int) ftell(in_file);
			fseek(in_file, 0, SEEK_SET);
			_swix(OS_File, _INR(0,1)|_OUT(6), 23, file, &desc.type.filetype);
			desc.flags = 0;

			printf("Requesting DiskSample to play from our dedicated input buffer\n");
			e = _swix(DiskSample_StreamSource, _INR(0,2)|_OUT(1), handle, 0, &desc, &inb);
		}
		else if (bIsUrl)
		{
			printf("Requesting DiskSample to play internet radio\n");
			e = _swix(DiskSample_StreamSource, _INR(0,3), handle, 2, file, proxy);
		}
		else if (bExtMode)
		{
			printf("Requesting DiskSample to play file by extension\n");
			desc.filesize = 0;
			desc.type.codecname = ext;
			desc.flags = 1;
			e = _swix(DiskSample_StreamSource, _INR(0,3), handle, 3, file, &desc);
		}
		else
		{
			printf("Requesting DiskSample to play file\n");
			e = _swix(DiskSample_StreamSource, _INR(0,2), handle, 1, file);
		}

		if (e) break;

		// play source if assignment is accepted
		if (bStreaming)
		{
			if (blksize > (inb->size >> 2))
				blksize = inb->size >> 2;

			printf("Buffering by %d in buffer of size %d ...\n", blksize, inb->size);
		}

		// Wait (with a timeout) till DiskSample has extracted playing info (samplerate, channels, ...)
		t=clock(); tout = t + 10*CLK_TCK;

		while((clock() < tout) && !isready && !e)
		{
			if (bStreaming)
			{
				// It can only extract the playing info if we gave him data to analyse first
				fill_buffer(blksize);
			}
			// Analysis of the data is done by DiskSample as a background process,
			// we only have to wait till it's done.
			e = _swix(DiskSample_StreamIsReady, _IN(0)|_OUT(1), handle, &isready);
		}

		if (e) break;

		// Play till end of source
		t = tin = clock();
		if (isready)
		{
			int th, tm, ts;
			printf("Disksample has identified the source's content and is ready to play\n");

			// Remove loop mode
			e = _swix(DiskSample_StreamStatus, _IN(0)|_IN(1)|_IN(2), handle, 8, 8);
			if (e) break;

			// Get stream probable duration info
			e = _swix(DiskSample_StreamInfo, _IN(0)|_OUT(2), handle, &pos);
			if (e) break;

			if (pos)
			{
				ts = pos /1000;
				tm = ts / 60; ts = ts % 60;
				th = tm / 60; tm = tm % 60;
				printf("Total Time: % 3d:%02d:%02d\n", th, tm, ts);
			}

			// Start to play
			e = _swix(DiskSample_StreamPlay, _IN(0), handle);
			if (e) break;

			// Loop till the stream is completely played
			status = 2;
			while(!e && (status & 2))
			{
				if (Keyboard_KeyPressed() == 27)
				{
					printf("Stop requested by user");
					break;
				}

				if (bStreaming)
				{
					// It can only if we gave him data to decode
					fill_buffer(blksize);
				}

				// Decoding and playing is done by DiskSample as a background process,
				// we only have to wait till it pauses at the end of the file.
				e = _swix(DiskSample_StreamStatus, _INR(0,2)|_OUT(1), handle, 0, 0, &status);
				if (e) break;

				// Show current playing time
				if ((clock() - t)> 100)
				{
					if (dstMode == 1)
					{
						int64_t bytes;

						e = _swix(DiskSample_StreamDecoding, _INR(0, 2), handle, 5, &bytes);
						if (e) break;
						bytes >>= 10;
						printf("\rKBytes: % 6lld", bytes);
					}
					else
					{
						e = _swix(DiskSample_StreamPosition, _IN(0)|_IN(1)|_OUT(1), handle, -1, &pos);
						if (e) break;
						ts = pos /1000;
						tm = ts / 60; ts = ts % 60;
						th = tm / 60; tm = tm % 60;
						printf("\rTime: % 3d:%02d:%02d", th, tm, ts);
					}

					t = clock();
				}
			}
			printf("\n");
		}
		else
		{
			fprintf(stderr, "The source's content does not seem to be playable by DiskSample\n");
		}

		// End of decoding
		break;
	}

	if (e) fprintf(stderr, "Error: %s\n", e->errmess);

	// Close the file/stream
	if (handle != -1) _swix(DiskSample_StreamClose, _IN(0), handle);

	if (bStreaming && (in_file != NULL)) fclose(in_file);

	if (e) return EXIT_FAILURE;

	return EXIT_SUCCESS;
}
